
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.util
{
import mx.collections.ArrayCollection;
import mx.collections.IViewCursor;

import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationsUpdate;
import org.waveprotocol.wave.model.util.Pair;
import org.waveprotocol.wave.model.util.Preconditions;


public class ImmutableAnnotationsUpdateMap implements UpdateMap {
	
	public var updates:ArrayList= null;
	public function changeSize():int {
		return updates.length;
	}
	
	public function getChangeKey(i:int):String {
		return (AttributeUpdate(updates.getItemAt(i))).name;
	}
	
	
	public function getOldValue(i:int):String {
		return (AttributeUpdate(updates.getItemAt(i))).oldValue;
	}
	
	
	public function getNewValue(i:int):String {
		return (AttributeUpdate(updates.getItemAt(i))).newValue;
	}
	
	public function ImmutableAnnotationsUpdateMap(al:ArrayList) {
		if (al == null)
			updates = new ArrayList();
		else
			updates = al;
	}
	
	//  public static ImmutableUpdateMap of(Map updates) {
	//	    return new ImmutableUpdateMap(of(tripletsFromMap(updates)));
	//  }
	
	
	public static function of(triples:Array):ArrayList {
		if (triples.length % 3!= 0) {
			throw new Error("Triples must come in groups of three");
		}
		
		var accu:ArrayList= new ArrayList(triples.length / 3);
		for (var i:int= 0; i < triples.length; i += 3) {
			Preconditions.checkNotNull(triples[i], "Null key");
			accu.addItem(new AttributeUpdate(triples[i], triples[i + 1], triples[i + 2]));
		}
		
		//    Collections.sort(accu, comparator);
		
		for (var i:int= 1; i < accu.length; i++) {
			var x:int= AttributeUpdate(accu.getItemAt(i - 1)).compareTo(AttributeUpdate(accu.getItemAt(i)));
			if (x == 0) {
				throw new Error("Duplicate key: " + (AttributeUpdate(accu.getItemAt(i))).name);
			}
			org.waveprotocol.wave.model.util.Assert.assert(x < 0);
		}
		return accu;
	}
	
	
	private static function tripletsFromMap(updates:HashMap):Array {
		var triplets:Array= [];
		var i:int= 0;
		//    Map<String, Pair> up = updates;
		for (var key:String in updates.keys) {
			triplets[i++] = key;
			triplets[i++] = String(Pair(updates.getItem(key)).getFirst());
			triplets[i++] = String(Pair(updates.getItem(key)).getSecond());
		}
		return triplets;
	}
	
	//  protected ImmutableUpdateMap(List<AttributeUpdate> updates) {
	//    this.updates = updates;
	//  }
	
	public function exclude(names:ArrayCollection):AnnotationsUpdate {
		var newAttributes:ArrayList= new ArrayList();
		//		ArrayList<AttributeUpdate> temp = updates;
		for (var c:Object in updates) {
			var update:AttributeUpdate =AttributeUpdate(c);
			if (!names.contains(update.name)) {
				newAttributes.addItem(update);
			}
		}
		return createFromList(newAttributes);
	}
	
	//  protected static final Comparator<AttributeUpdate> comparator = new Comparator<AttributeUpdate>() {
	//    
	//    public function compare(a:AttributeUpdate, b:AttributeUpdate):int {
	//      return a.name.compareTo(b.name);
	//    }
	//  };
	
	public function composeWith(mutation:AnnotationsUpdate):AnnotationsUpdate {
		var newAttributes:ArrayList= new ArrayList();
		var iterator:IViewCursor = updates.createCursor();
		var nextAttribute:AttributeUpdate= AttributeUpdate(iterator.current);
		iterator.moveNext();
		// TODO: Have a slow path when the cast would fail.
		//    ArrayList<AttributeUpdate> mutationAttributes = ;
		var size:int = (ImmutableAnnotationsUpdateMap(mutation)).updates.length;
		myloop: for(var i:int=0; i< size; i++) {
			var attribute:AttributeUpdate = AttributeUpdate((ImmutableAnnotationsUpdateMap(mutation)).updates.getItemAt(i));
			while (nextAttribute != null) {
				var comparison:int= attribute.compareTo(nextAttribute);
				if (comparison < 0) {
					break;
				} else if (comparison > 0) {
					newAttributes.addItem(nextAttribute);
					nextAttribute = AttributeUpdate(iterator.current);
					iterator.moveNext();
				} else {
					if (!areEqual(nextAttribute.newValue, attribute.oldValue)) {
						throw new Error("Mismatched old value: attempt to update " +
							nextAttribute + " with " + attribute);
					}
					newAttributes.addItem(new AttributeUpdate(attribute.name, nextAttribute.oldValue,
						attribute.newValue));
					nextAttribute = AttributeUpdate(iterator.current);
					iterator.moveNext();
					continue myloop;
				}
			}
			newAttributes.addItem(attribute);
		}
		if (nextAttribute != null) {
			newAttributes.addItem(nextAttribute);
			while (!iterator.afterLast) {
				newAttributes.addItem(iterator.current);
				iterator.moveNext();
			}
		}
		return createFromList(newAttributes);
	}
	
	protected function createFromList(attributes:ArrayList):AnnotationsUpdate { return null;} ;
	
	// TODO: Is there a utility method for this somewhere?
	private function areEqual(a:Object, b:Object):Boolean {
		return (a == null) ? b == null : a.equals(b);
	}
	
	
	public function toString():String {
		return "Updates: " + updates;
	}
	
	public static function checkUpdatesSorted(updates:ArrayList):void {
		var previous:AttributeUpdate= null;
		//    ArrayList<AttributeUpdate> temp = updates;
		for (var o:Object in updates) {
			var u:AttributeUpdate = AttributeUpdate(o);
			Preconditions.checkNotNull(u, "Null attribute update");
			org.waveprotocol.wave.model.util.Assert.assert(u.name != null);
			if (previous != null) {
				Preconditions.checkArgument(previous.name.localeCompare(u.name) < 0,
					"Attribute keys not strictly monotonic: " + previous.name + ", " + u.name);
			}
			previous = u;
		}
	}
	
}
}